package dao;

import util.CustomException;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.util.List;
import javax.persistence.SequenceGenerator;
import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.criterion.Criterion;

/**
 * Classe padrão para controle de persistência de objetos utilizando Hibernate.
 *
 * @author Rodolpho
 */
public class GenericEntityDAO<T, ID extends Serializable> {

    private Class<T> persistentClass;
    private String sequenceName;

    /**
     * Construtor. Obtém a classe persistente.
     *
     */
    public GenericEntityDAO() {
        this.persistentClass = (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
        loadSequenceName();
    }

    private void loadSequenceName() {
        try {
            for (Method method : persistentClass.getDeclaredMethods()) {
                if ((method.getAnnotation(SequenceGenerator.class) != null)) {
                    this.sequenceName = method.getAnnotation(SequenceGenerator.class).sequenceName();
                    break;
                }
            }
        } catch (Exception e) {
            this.sequenceName = "";
        }
    }

    public Class<T> getPersistentClass() {
        return persistentClass;
    }

    public void setPersistentClass(Class<T> persistentClass) {
        this.persistentClass = persistentClass;
    }

    /**
     * Retorna a lista contendo todos os objetos da entidade do banco de dados.
     *
     * @return List<T>
     * @throws CustomException
     */
    @SuppressWarnings("unchecked")
    public List<T> selecionarTodos(Session session) throws CustomException {
        return selecionarListaPorCriterio(session);
    }

    /**
     * Retorna o objeto do banco de dados de acordo com o id passado por
     * parâmetro.
     *
     * @param id
     * @return List<T>
     * @throws CustomException
     */
    @SuppressWarnings("unchecked")
    public T selecionarPorId(Session session, ID id) throws CustomException {

        T entity = null;

        try {
            entity = (T) session.get(getPersistentClass(), id);
        } catch (Exception e) {
            throw new CustomException("Falha ao selecionar o registro " + id + " do objeto/entidade " + getPersistentClass().getSimpleName() + ".", e);
        }

        return entity;
    }

    /**
     * Persiste o objeto passado por parâmetro no banco de dados.
     *
     * @param entity
     * @return boolean
     * @throws CustomException
     */
    @SuppressWarnings("unchecked")
    public boolean salvar(Session session, T entity) throws CustomException {
        try {
            session.merge(entity);
            return true;
        } catch (Exception e) {
            throw new CustomException("Falha ao incluir o objeto/entidade " + getPersistentClass().getSimpleName() + ".", e);
        }
    }

    /**
     * Exclui o objeto passado por parâmetro no banco de dados.
     *
     * @return boolean
     * @param entity
     * @throws CustomException
     */
    public boolean excluir(Session session, T entity) throws CustomException {
        try {
            session.delete(entity);
            return true;
        } catch (Exception e) {
            throw new CustomException("Falha ao excluir o objeto/entidade " + getPersistentClass().getSimpleName() + ".", e);
        }
    }

    /**
     * Retorna a lista de objetos de acordo com os critérios passados por
     * parâmetro.
     *
     * @param criterion
     * @return List<T>
     * @throws CustomException
     */
    @SuppressWarnings("unchecked")
    public List<T> selecionarListaPorCriterio(Session session, Criterion... criterion) throws CustomException {
        List<T> list = null;

        try {

            Criteria crit = session.createCriteria(getPersistentClass());
            for (Criterion c : criterion) {
                if (c != null) {
                    crit.add(c);
                }
            }
            list = crit.list();
        } catch (Exception e) {
            throw new CustomException("Falha ao selecionar registros por critério do objeto/entidade " + getPersistentClass().getSimpleName() + ".", e);
        }

        return list;
    }

    /**
     * Retorna a lista de objetos de acordo com os critérios passados por
     * parâmetro.
     *
     * @param criterion
     * @return List<T>
     * @throws CustomException
     */
    @SuppressWarnings("unchecked")
    public T selecionarRegistroPorCriterio(Session session, Criterion... criterion) throws CustomException {
        T entity = null;

        try {
            Criteria crit = session.createCriteria(getPersistentClass());
            for (Criterion c : criterion) {
                if (c != null) {
                    crit.add(c);
                }
            }

            List list = crit.list();
            if (!(list.isEmpty())) {
                entity = (T) crit.list().get(0);
            }
        } catch (Exception e) {
            throw new CustomException("Falha ao selecionar registros por critério do objeto/entidade " + getPersistentClass().getSimpleName() + ".", e);
        }

        return entity;
    }

    /**
     * Retorna o próximo valor da sequence da entidade.
     *
     * @param session
     * @return ID
     * @throws CustomException
     */
    public ID selecionarProximoId(Session session) throws CustomException {
        ID id = null;
        try {
            Long seqValue = ((Number) session.createSQLQuery("VALUES NEXT VALUE FOR " + sequenceName).uniqueResult()).longValue();
            id = (ID) seqValue;
        } catch (Exception e) {
            throw new CustomException("Falha ao obter o próximo valor da sequence " + sequenceName, e);
        }
        return id;
    }
}
